﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using Microsoft.SharePoint;
using Navigant.Infrastructure.Cases;
using Navigant.Infrastructure.ClientPortals;
using Navigant.Infrastructure.Helpers;
using Navigant.Model.Cases;
using Navigant.Model.ClientPortals;

namespace Navigant.Infrastructure.Repositories
{
    public class SharePointClientPortalRepository : IClientPortalRepository
    {
        #region Constants

        private static class Constants
        {
            public const string ConfigurationSettingNameField = "Title";
            public const string ConfigurationSettingValueField = "Value";
            public const string SiteSettingsListName = "Site Settings";
        }

        #endregion

        #region IClientPortalRepository Members

        public IClientPortal FindBy(Uri siteAddress)
        {
            using (SPSite site = new SPSite(siteAddress.AbsoluteUri))
            {
                return this.GetClientPortalFromSite(site);
            }
        }

        public void SetSetting(IClientPortal clientPortal, string name, string value)
        {
            using (SPSite site = new SPSite(clientPortal.SiteAddress.AbsoluteUri))
            {
                using (SPWeb web = site.RootWeb)
                {
                    SPListItemCollection settings = this.GetSettingsItems(web);
                    SPListItem settingToSet = null;
                    foreach (SPListItem setting in settings)
                    {
                        if (setting[Constants.ConfigurationSettingNameField].ToString() == name)
                        {
                            settingToSet = setting;
                            break;
                        }
                    }
                    if (settingToSet == null)
                    {
                        settingToSet = settings.Add();
                        settingToSet[Constants.ConfigurationSettingNameField] = name;
                    }

                    settingToSet[Constants.ConfigurationSettingValueField] = value;
                    settingToSet.Update();
                }
            }
        }

        public IClientPortal FindBy(Guid siteCollectionKey)
        {
            IClientPortal portal = null;
            using (SPSite site = new SPSite(siteCollectionKey))
            {
                portal = this.GetClientPortalFromSite(site);
            }
            return portal;
        }

        public IList<ICase> FindAllCases(Guid siteCollectionKey)
        {
            List<ICase> cases = new List<ICase>();
            ICase caseSite = null;
            using(SPSite site = new SPSite(siteCollectionKey))
            {
                foreach (SPWeb web in site.AllWebs)
                {
                    caseSite = CaseService.GetCaseBy(siteCollectionKey, web.ID);
                    if (caseSite != null)
                    {
                        cases.Add(caseSite);
                    }
                    web.Dispose();
                }
            }
            return cases;
        }

        /// <summary>
        /// Gets a security-trimmed list of sites from the Site Directory sorted by name in ascending order.
        /// </summary>
        /// <param name="siteCollectionKey"></param>
        /// <param name="siteDirectoryName"></param>
        /// <param name="categoryName"></param>
        /// <param name="categoryValue"></param>
        /// <returns></returns>
        public IList<ICase> FindAllCases(Guid siteCollectionKey, string siteDirectoryName, 
            string categoryName, string categoryValue)
        {
            List<ICase> cases = new List<ICase>();
            using (SPSite site = new SPSite(siteCollectionKey))
            {
                using (SPWeb web = site.AllWebs[siteDirectoryName])
                {
                    SPList directorySites = web.Lists["Sites"];
                    string url = null;
                    string itemCategory = null;
                    foreach (SPListItem directorySiteItem in directorySites.Items)
                    {
                        url = this.GetFieldValue("URL", directorySiteItem);
                        url = url.Substring(0, url.IndexOf(","));
                        itemCategory = this.GetFieldValue(categoryName, directorySiteItem);

                        if (string.IsNullOrEmpty(categoryName) ||
                            (categoryValue == "All Sites") ||
                            (itemCategory == categoryValue))
                        {
                            using (SPSite directorySite = this.GetSharePointSiteByUrl(url, site))
                            {
                                using (SPWeb directoryWeb = directorySite.OpenWeb())
                                {
                                    if (url.ToLower().Contains(directoryWeb.Url.ToLower()) &&
                                        this.HasPermissions(directoryWeb))
                                    {
                                        cases.Add(CaseFactory.BuildEntity(directoryWeb.ID, directoryWeb.Title, 
                                            new Uri(directoryWeb.Url), siteCollectionKey));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return cases;
        }

        public IList<string> GetCaseSiteCategories(Guid clientPortalId,
            string siteDirectoryName, string categoryName)
        {
            List<string> choices = new List<string>();
            try
            {
                using (SPSite site = new SPSite(clientPortalId))
                {
                    using (SPWeb web = site.AllWebs[siteDirectoryName])
                    {
                        SPList directorySites = web.Lists["Sites"];
                        string possibleValuesXml = directorySites.Fields[categoryName].SchemaXml;
                        XmlDocument directorySitesSchema = new XmlDocument();
                        directorySitesSchema.LoadXml(possibleValuesXml);
                        XmlNodeList choicesList = directorySitesSchema.SelectNodes("//Field/CHOICES/CHOICE");
                        foreach (XmlNode node in choicesList)
                        {
                            choices.Add(node.InnerText);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
            return choices;
        }

        public string GetDefaultCaseSiteCategory(Guid clientPortalId,
            string siteDirectoryName, string categoryName)
        {
            string defaultCategory = string.Empty;
            try
            {
                using (SPSite site = new SPSite(clientPortalId))
                {
                    using (SPWeb web = site.AllWebs[siteDirectoryName])
                    {
                        SPList directorySites = web.Lists["Sites"];
                        string possibleValuesXml = directorySites.Fields[categoryName].SchemaXml;
                        XmlDocument directorySitesSchema = new XmlDocument();
                        directorySitesSchema.LoadXml(possibleValuesXml);
                        XmlNode defaultCategoryValueNode = directorySitesSchema.SelectSingleNode("//Field/Default");
                        if (defaultCategoryValueNode != null)
                        {
                            defaultCategory = defaultCategoryValueNode.InnerText;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
            return defaultCategory;
        }

        public void SaveXmlFileToDocumentLibrary(IClientPortal clientPortal, 
            XmlDocument document, string documentLibraryName, string fileName)
        {
            using (SPSite site = new SPSite(clientPortal.Id))
            {
                using (SPWeb web = site.RootWeb)
                {
                    SPFolder documentsFolder = null;
                    try
                    {
                        documentsFolder = web.GetFolder(documentLibraryName);
                    }
                    catch
                    {
                        Logger.LogWarning(string.Format("The {0} site collection does not contain the {1} document library.",
                            site.Url, documentLibraryName));
                    }
                    if (documentsFolder != null)
                    {
                        using (MemoryStream stream = new MemoryStream())
                        {
                            document.Save(stream);
                            documentsFolder.Files.Add(fileName, stream, true);
                        }
                    }
                }
            }
        }

        #endregion

        #region HasPermissions

        private bool HasPermissions(SPWeb web)
        {
            bool hasPermissions = false;

            try
            {
                if (web != null)
                {
                    web.Site.CatchAccessDeniedException = false;
                    hasPermissions = web.DoesUserHavePermissions(
                            SPBasePermissions.ViewPages);
                }
            }
            catch
            {
                hasPermissions = false;
            }
            finally
            {
                if (web != null)
                {
                    web.Site.CatchAccessDeniedException = true;
                }
            }

            return hasPermissions;
        }

        #endregion

        #region GetSharePointSiteByUrl

        /// <summary>
        /// This is a factory method added to isolate the problem of 
        /// trying to create an SPSite instance from a URL.  Sometimes 
        /// using the DNS host name causes an error if the site was 
        /// registered in the SharePoint config database with an IP address.
        /// </summary>
        /// <param name="url">The URL of the site to open.</param>
        /// <returns>An <see cref="Microsoft.Sharepoint.SPSite"/> instance.</returns>
        private SPSite GetSharePointSiteByUrl(string url, SPSite currentSite)
        {
            SPSite site = null;
            string fullUrl = url;

            // Validate the URL, see if it includes the protocol or not
            // Sometimes sites in the Site Directory will only be stored 
            // like this:  /sites/test
            if (!url.StartsWith("http"))
            {
                // Need to build a proper URL
                fullUrl = string.Format("{0}//{1}{2}",
                    currentSite.Protocol, currentSite.HostName, url);
            }

            try
            {
                site = new SPSite(fullUrl);
            }
            catch //(Exception e)
            {
                //Logger.LogError(e);
            }
            return site;
        }

        #endregion

        #region GetFieldValue

        private string GetFieldValue(string fieldName, SPListItem item)
        {
            string fieldValue = string.Empty;
            try
            {
                fieldValue = item[fieldName].ToString();
            }
            catch //(Exception e)
            {
                //Logger.LogError(e);
            }
            return fieldValue;
        }

        #endregion

        #region GetClientPortalFromSite

        private IClientPortal GetClientPortalFromSite(SPSite site)
        {
            ClientPortal clientPortal = null;
            try
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    if (site == null)
                    {
                        throw new ArgumentException("Must have a valid SPSite instance", "site");
                    }
                    if (this.IsValidClientPortal(site))
                    {
                        using (SPWeb web = site.RootWeb)
                        {
                            string ownerEmail = string.Empty;
                            string secondaryContactEmail = string.Empty;
                            try
                            {
                                if (site.Owner != null &&
                                    !string.IsNullOrEmpty(site.Owner.Email))
                                {
                                    ownerEmail = site.Owner.Email;
                                }

                                if (site.SecondaryContact != null &&
                                    !string.IsNullOrEmpty(site.SecondaryContact.Email))
                                {
                                    secondaryContactEmail = site.SecondaryContact.Email;
                                }
                            }
                            catch (Exception e)
                            {
                                Logger.LogError(e);
                            }
                            clientPortal = new ClientPortal(site.ID, web.Title,
                                new Uri(site.Url), ownerEmail, secondaryContactEmail);
                        }
                        this.LoadSiteSettings(site, clientPortal);
                    }
                });
            }
            catch (Exception e)
            {
                Logger.LogError(e);
            }
            return clientPortal;
        }

        #endregion

        #region IsValidClientPortal

        private bool IsValidClientPortal(SPSite site)
        {
            bool valid = false;
            using (SPWeb web = site.RootWeb)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.Title == Constants.SiteSettingsListName)
                    {
                        valid = true;
                        break;
                    }
                }
            }
            return valid;
        }

        #endregion

        #region LoadSiteSettings

        private void LoadSiteSettings(SPSite site, ClientPortal clientPortal)
        {
            SPListItemCollection settings = null;
            using (SPWeb web = site.RootWeb)
            {
                settings = this.GetSettingsItems(web);
                if (settings != null)
                {
                    foreach (SPListItem setting in settings)
                    {
                        // Guard against null entries in the list
                        try
                        {
                            string key = string.Empty;
                            if (setting[Constants.ConfigurationSettingNameField] != null)
                            {
                                key = setting[Constants.ConfigurationSettingNameField].ToString();
                            }
                            if (key.Length > 0 &&
                                !clientPortal.ConfigurationSettings.ContainsKey(key) &&
                                setting[Constants.ConfigurationSettingValueField] != null)
                            {
                                clientPortal.ConfigurationSettings.Add(key,
                                    setting[Constants.ConfigurationSettingValueField].ToString());
                            }
                        }
                        catch { }
                    }
                }
            }
        }

        #endregion

        #region GetSettingsItems

        private SPListItemCollection GetSettingsItems(SPWeb web)
        {
            foreach (SPList list in web.Lists)
            {
                if (list.Title == Constants.SiteSettingsListName)
                {
                    return list.Items;
                }
            }
            return null;
        }

        #endregion
    }
}
